/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*/

#include <asm/current.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "securec.h"
#include "dvpp_cmdlist_log.h"
#include "dvpp_cmdlist_ioctl.h"

static cmdlist_case g_cmdlist_case = {GEN_CMDLIST, NULL};

static dev_t g_dvpp_cmdlist_dev_num = 0;
static struct class *g_dvpp_cmdlist_class;
static struct cdev g_dvpp_cmdlist_cdev;
static struct device *g_dvpp_cmdlist_device;

static void dvpp_cmdlist_get_pid(int *p_pid)
{
    *p_pid = current->tgid;
}

static int32_t dvpp_awake_handler(unsigned int cmd, void *user_data)
{
    int32_t ret;
    dvpp_cmdlist_ioctl_args ioctl_args;
    CMDLIST_HANDLER handler = NULL;

    if (cmd == g_cmdlist_case.cmd) {
        handler = g_cmdlist_case.handler;
    } else {
        DVPP_CMDLIST_LOG_ERROR("check ioctl cmd fail, cmd %x should be %x."
                               "pls check whether the versions of the runtime pkg and the driver pkg are same.\n",
                               cmd, g_cmdlist_case.cmd);
        return -1;
    }

    if (handler == NULL) {
        DVPP_CMDLIST_LOG_ERROR("get function handler fail.\n");
        return -1;
    }

    dvpp_cmdlist_get_pid(&ioctl_args.info.pid);
    ioctl_args.user_data = user_data;

    ret = handler(&ioctl_args);
    if (ret != 0) {
        DVPP_CMDLIST_LOG_ERROR("handler fail");
        return ret;
    }
    return 0;
}

static int dvpp_cmdlist_open(struct inode *node, struct file *file)
{
    // do nothing
    (void)node;
    (void)file;
    return 0;
}

static int dvpp_cmdlist_close(struct inode *node, struct file *file)
{
    // do nothing
    (void)node;
    (void)file;
    return 0;
}

static long dvpp_cmdlist_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    void *user_data = (void *)(uintptr_t)arg;
    long ret = -1;

    if ((file == NULL) || (user_data == NULL)) {
        DVPP_CMDLIST_LOG_ERROR("check ioctl input fail");
        return -1;
    }
    ret = dvpp_awake_handler(cmd, user_data);
    return ret;
}

static const struct file_operations dvpp_cmdlist_fops = {
    .owner = THIS_MODULE,
    .open = dvpp_cmdlist_open,
    .release = dvpp_cmdlist_close,
    .unlocked_ioctl = dvpp_cmdlist_ioctl
};

int32_t dvpp_cmdlist_dev_init(void)
{
    int32_t ret = 0;

    // 内核自动分配设备号
    ret = alloc_chrdev_region(&g_dvpp_cmdlist_dev_num, 0, DVPP_CMDLIST_DEVICE_CNT, DVPP_CMDLIST_DEVICE_NAME);
    if (ret < 0) {
        DVPP_CMDLIST_LOG_ERROR("alloc_chrdev_region fail, ret = %d", ret);
        goto err_region;
    }

    // 创建一个设备类
    g_dvpp_cmdlist_class = class_create(DVPP_CMDLIST_DEVICE_NAME);
    if (IS_ERR(g_dvpp_cmdlist_class)) {
        ret = -1;
        DVPP_CMDLIST_LOG_ERROR("class_create fail");
        goto err_class;
    }

    // 将file_operations填充进去
    cdev_init(&g_dvpp_cmdlist_cdev, &dvpp_cmdlist_fops);
    g_dvpp_cmdlist_cdev.owner = THIS_MODULE;

    // 将设备体与设备号绑定并向内核注册一个字符设备
    ret = cdev_add(&g_dvpp_cmdlist_cdev, g_dvpp_cmdlist_dev_num, DVPP_CMDLIST_DEVICE_CNT);
    if (ret) {
        DVPP_CMDLIST_LOG_ERROR("cdev_add fail, ret = %d", ret);
        goto err_cdev;
    }

    // 创建设备文件
    g_dvpp_cmdlist_device = device_create(g_dvpp_cmdlist_class, NULL, g_dvpp_cmdlist_dev_num,
                                          NULL, DVPP_CMDLIST_DEVICE_NAME);
    if (IS_ERR(g_dvpp_cmdlist_device)) {
        ret = -1;
        DVPP_CMDLIST_LOG_ERROR("device_create faild");
        goto err_device;
    }

    return 0;

err_device:
    cdev_del(&g_dvpp_cmdlist_cdev);
err_cdev:
    class_destroy(g_dvpp_cmdlist_class);
err_class:
    unregister_chrdev_region(g_dvpp_cmdlist_dev_num, DVPP_CMDLIST_DEVICE_CNT);
err_region:
    return ret;
}

int32_t dvpp_get_gen_cmdlist_info_from_ioctl(dvpp_cmdlist_ioctl_args *arg,
    dvpp_gen_cmdlist_user_data *user_data, int32_t *pid, uint32_t *devid, uint32_t *phyid, struct dvpp_sqe *sqe)
{
    unsigned long ret;
    if (arg->user_data == NULL) {
        DVPP_CMDLIST_LOG_ERROR("arg->user_data is NULL");
        return -1;
    }
    *pid = arg->info.pid;
    ret = copy_from_user(user_data, arg->user_data, sizeof(dvpp_gen_cmdlist_user_data));
    if (ret != 0) {
        DVPP_CMDLIST_LOG_ERROR("copy from user user_data fail");
        return -1;
    }
    if ((user_data->sqe == NULL) || (user_data->sqe_len != sizeof(struct dvpp_sqe))) {
        DVPP_CMDLIST_LOG_ERROR("user_data check fail");
        return -1;
    }
    *devid = user_data->devid;
    *phyid = user_data->phyid;
    ret = copy_from_user(sqe, user_data->sqe, sizeof(struct dvpp_sqe));
    if (ret != 0) {
        DVPP_CMDLIST_LOG_ERROR("copy from user sqe fail");
        return -1;
    }
    return 0;
}

int32_t dvpp_set_gen_cmdlist_sqe_to_user_data(struct dvpp_sqe *sqe, dvpp_gen_cmdlist_user_data *user_data)
{
    unsigned long ret;
    ret = copy_to_user(user_data->sqe, sqe, sizeof(struct dvpp_sqe));
    if (ret != 0) {
        DVPP_CMDLIST_LOG_ERROR("copy to user fail");
        return -1;
    }
    return 0;
}

void dvpp_cmdlist_dev_exit(void)
{
    device_destroy(g_dvpp_cmdlist_class, g_dvpp_cmdlist_dev_num);
    cdev_del(&g_dvpp_cmdlist_cdev);
    class_destroy(g_dvpp_cmdlist_class);
    unregister_chrdev_region(g_dvpp_cmdlist_dev_num, DVPP_CMDLIST_DEVICE_CNT);
}

int32_t dvpp_set_gen_cmdlist_func(cmdlist_case *cmd_case)
{
    if (cmd_case->cmd != GEN_CMDLIST) {
        DVPP_CMDLIST_LOG_ERROR("set cmdlist func fail cmd=%d", (int32_t)cmd_case->cmd);
        return -1;
    }

    g_cmdlist_case.handler = cmd_case->handler;
    return 0;
}
